#include <iostream>

#include "camera.hpp"
#include "city.hpp"
#include "draw_world.hpp"
#include "foreach.hpp"
#include "gamemap.hpp"
#include "government.hpp"
#include "hex_geometry.hpp"
#include "raster.hpp"
#include "tile.hpp"
#include "unit.hpp"
#include "world.hpp"

namespace hex {

int renderer::current_frame_ = 0;

renderer::renderer(const world& w, const government& perspective)
  : world_(w), map_(w.map()), perspective_(perspective)
{}

void renderer::draw_world(const camera& cam, const hex::location& focus, const migrations_list& migrations, const route_map* routes)
{
	++current_frame_;

	const int xtile = -cam.get_pan_x()/XRatio;
	const int ytile = -cam.get_pan_y();

	//some quick and dirty logic to work out which tiles are 'on-screen'
	//and thus should be drawn.
	GLfloat zoom = -cam.zoom();
	if(zoom < 25.0) {
		zoom = 25.0;
	}


	int x1 = xtile - zoom/4 - 2;
	int x2 = xtile + zoom/4;
	int y1 = ytile - zoom/4;
	int y2 = ytile + zoom/4 + 4;

	if(x1 < 0) {
		x1 = 0;
	}

	if(y1 < 0) {
		y1 = 0;
	}

	if(x2 > map_.width()) {
		x2 = map_.width();
	}

	if(y2 > map_.height()) {
		y2 = map_.height();
	}

	//some logic to lower the detail as we zoom out.
	int max_detail = 2;
	const heightmap* heights = &map_.heights();
	if(zoom > 60.0) {
		max_detail = 1;
		if(zoom > 100.0) {
			max_detail = 0;
		}
	}

	if(heights->subdivisions() > max_detail) {
		heights = heights->lower_detail_version(max_detail);
	}

	for(int y = y1; y < y2; ++y) {
		for(int x = x1; x < x2; ++x) {
			if(!perspective_.is_unshrouded(x, y)) {
				continue;
			}
			location loc(x, y);
			bool dim_color = routes && routes->count(loc) == 0;
			if(dim_color) {
				glColor4f(0.5, 0.5, 0.5, 1.0);
			}
			map_.get_tile(loc)->draw(*heights);
			if(dim_color) {
				glColor4f(1.0, 1.0, 1.0, 1.0);
			}
		}
	}

	foreach(const river_ptr& r, map_.rivers()) {
		r->draw();
	}
	
	for(int y = y1; y < y2; ++y) {
		for(int x = x1; x < x2; ++x) {
			if(!perspective_.is_unshrouded(x, y)) {
				continue;
			}
			location loc(x, y);
			map_.get_tile(loc)->draw_infrastructure();
			map_.get_tile(loc)->draw_borders(world_);
		}
	}

	if(map_.is_loc_on_map(focus)) {
		//draw the cursor on the selected tile.
		map_.get_tile(focus)->draw(*heights, graphics::texture::get("gui/hex-active-unit.png"), 0.02);
	}

	for(int y = y1; y < y2; ++y) {
		for(int x = x1; x < x2; ++x) {
			if(!perspective_.is_unshrouded(x, y)) {
				continue;
			}
			location loc(x, y);
			const const_tile_ptr t = map_.get_tile(loc);
			if(t->city()) {
				t->city()->draw(world_);
			}
		}
	}

	for(int y = y1; y < y2; ++y) {
		for(int x = x1; x < x2; ++x) {
			if(!perspective_.is_unshrouded(x, y)) {
				continue;
			}
			location loc(x, y);
			const unit_list& units = world_.units_on_tile(loc);
			if(units.empty()) {
				continue;
			}

			const int xpos = tile_pixel_x(loc);
			const int ypos = tile_pixel_y(loc);
			foreach(const unit_ptr& u, units) {
				u->draw(xpos, ypos);
			}
		}
	}


	if(map_.is_loc_on_map(focus)) {
		map_.get_tile(focus)->draw_description();
	}

	glDisable(GL_TEXTURE_2D);
	glBegin(GL_LINES);
	foreach(const migration_pair& migration, migrations) {
		if(!perspective_.is_unshrouded(migration.first.x(), migration.first.y()) &&
		   !perspective_.is_unshrouded(migration.second.x(), migration.second.y())) {
			continue;
		}

		const int x1 = tile_pixel_x(migration.first) + HexSize/2;
		const int y1 = tile_pixel_y(migration.first) + HexSize/2;
		const int x2 = tile_pixel_x(migration.second) + HexSize/2;
		const int y2 = tile_pixel_y(migration.second) + HexSize/2;

		glColor4f(1.0, 0.0, 0.0, 1.0);
		glLineWidth(5);
		glVertex3f(x1, y1, 0);
		glLineWidth(1);
		glVertex3f(x2, y2, 0);
		glColor4f(1.0, 1.0, 1.0, 1.0);
	}
	glEnd();

	glBegin(GL_LINES);
	foreach(const_city_ptr c, world_.cities()) {
		foreach(const merchant_trade& trade, c->exports()) {
			for(int n = 0; n < trade.path->size() - 1; ++n) {
				if(!perspective_.is_unshrouded((*trade.path)[n].x(), (*trade.path)[n].y()) &&
				   !perspective_.is_unshrouded((*trade.path)[n+1].x(), (*trade.path)[n+1].y())) {
					continue;
				}

				const int x1 = tile_pixel_x((*trade.path)[n]) + HexSize/2;
				const int y1 = tile_pixel_y((*trade.path)[n]) + HexSize/2;
				const int x2 = tile_pixel_x((*trade.path)[n+1]) + HexSize/2;
				const int y2 = tile_pixel_y((*trade.path)[n+1]) + HexSize/2;
				glColor4f(0.0, 0.0, 0.0, 0.7);
				glLineWidth(8);
				glVertex3f(x1, y1, 0);
				glColor4f(1.0, 0.0, 0.0, 0.7);
				glVertex3f(x2, y2, 0);
				glLineWidth(1);
				glColor4f(1.0, 1.0, 1.0, 1.0);
			}
		}
	}
	glEnd();
	glEnable(GL_TEXTURE_2D);
}

bool renderer::get_selected_hex(camera& cam, hex::location* result) const
{
	assert(result);

	const heightmap* heights = map_.heights().lower_detail_version(0);
	assert(heights);

	int mousex, mousey;
	SDL_GetMouseState(&mousex, &mousey);
	cam.prepare_selection(mousex, mousey);

	if(map_.is_loc_on_map(*result)) {
		//try seeing if the currently selected hex is still selected.
		glLoadName(0);
		map_.get_tile(*result)->draw(*heights);
		std::vector<GLuint> selection;
		if(cam.finish_selection(&selection) != 0) {
			//try hexes surrounding the selected hex, since they are very
			//likely to be selected now.
			cam.prepare_selection(mousex, mousey);

			hex::location adj[6];
			get_adjacent_tiles(*result, adj);
			for(GLuint n = 0; n != 6; ++n) {
				if(!map_.is_loc_on_map(adj[n])) {
					continue;
				}
				glLoadName(n);
				map_.get_tile(adj[n])->draw(*heights);
			}

			const GLuint select_name = cam.finish_selection(&selection);
			if(select_name != GLuint(-1)) {
				*result = adj[select_name];
				return true;
			}

			//the currently selected hex is no longer selected. Do a full
			//test of what is now selected.
			*result = hex::location();
			return get_selected_hex(cam, result);

		} else {
			return false;  // no change in selection.
		}
	}

	GLuint select_name = 0;
	const std::vector<tile_ptr>& tiles = map_.tiles();
	for(std::vector<tile_ptr>::const_iterator t = tiles.begin(); t != tiles.end(); ++t) {
		glLoadName(select_name);
		(*t)->draw(*heights);
		++select_name;
	}

	std::vector<GLuint> selection;
	select_name = cam.finish_selection(&selection);
	if(select_name == GLuint(-1)) {
		*result = hex::location();
	} else {
		*result = tiles[select_name]->loc();
	}

	return true;
}

}
